package cn.share.life.common.sensitiveword.aspect;

import cn.hutool.dfa.WordTree;
import cn.share.life.common.exception.ServiceException;
import cn.share.life.common.sensitiveword.annotation.SensitiveWordTag;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 基于 Spring Aop 的注解鉴定敏感内容
 *
 * @author cuixj
 */
@Aspect
@Component
public class SensitiveWordAspect {

    /**
     * 定义AOP签名 (切入敏感字注解的方法)
     */
    public static final String POINTCUT_SIGN = "@annotation(com.hopefound.common.sensitiveword.annotation.SensitiveWordTag)";
    @Autowired
    private RemoteUserService remoteUserService;

    @Autowired
    private RedisService redisService;

    /**
     * 构建
     */
    public SensitiveWordAspect() {
    }

    /**
     * 声明AOP签名
     */
    @Pointcut(POINTCUT_SIGN)
    public void pointcut() {
    }

    /**
     * 环绕切入
     *
     * @param joinPoint 切面对象
     * @return 底层方法执行后的返回值
     * @throws Throwable 底层方法抛出的异常
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        SensitiveWordTag sensitiveWordTag = signature.getMethod().getAnnotation(SensitiveWordTag.class);
        if (sensitiveWordTag != null) {
            Object[] args = joinPoint.getArgs();
            List<String> contentList = new ArrayList<>();
            for (Object arg : args) {
                if (arg != null) {
                    findStringParamsContent(arg, contentList);
                }
            }
            String contents = contentList.stream().collect(Collectors.joining(","));
            if (StringUtils.isNotEmpty(contents)) {
                List<String> sensitiveWordAllList = findSensitiveWord(contents);
                if (CollectionUtils.isNotEmpty(sensitiveWordAllList)) {
                    String sensitiveWordMsg = sensitiveWordAllList.stream().collect(Collectors.joining(","));
                    remoteUserService.updateSensitiveWordCount(sensitiveWordAllList);
                    throw new ServiceException("请求参数存在敏感词:" + sensitiveWordMsg);
                }
            }
        }
        try {
            // 执行原有逻辑
            Object obj = joinPoint.proceed();
            return obj;
        } catch (Throwable e) {
            throw e;
        }
    }

    /**
     * 查询字符串参数内容
     *
     * @param arg
     * @param contentList
     * @return
     * @throws IllegalAccessException
     */
    private void findStringParamsContent(Object arg, List<String> contentList) throws IllegalAccessException {
        if (arg instanceof String) {
            String content = (String) arg;
            contentList.add(content);
        } else if (arg instanceof List) {
            List argList = (List) arg;
            for (Object obj : argList) {
                if (obj instanceof String && obj != null) {
                    contentList.add((String) obj);
                }
            }
        } else if (arg instanceof Map) {
            Map map = (Map) arg;
            map.forEach((k, v) -> {
                if (v instanceof String && v != null) {
                    contentList.add((String) v);
                }
            });
        } else {
            Class<?> clazz = arg.getClass();
            //如果是实体类，就需要根据class获取字段的类型
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                Class<?> type = declaredField.getType();
                if (type == String.class && !Modifier.toString(declaredField.getModifiers()).contains("final")) {
                    //如果是String类型,进行关键词匹配 且要排除final修饰的字段
                    declaredField.setAccessible(true);
                    Object obj = declaredField.get(arg);
                    if (obj != null) {
                        contentList.add(obj.toString());
                    }
                }
            }
        }
    }

    /**
     * 解析敏感词
     *
     * @param content
     * @return
     */
    private List<String> findSensitiveWord(String content) {
        //当敏感词数据量过大时，此处可以改为定时更新，减少redis查询次数和WordTree初始化的次数
        WordTree sensitiveWordTree = new WordTree();
        List<String> sensitiveWordList = redisService.getCacheObject(CacheConstants.SENSITIVE_WORD_LIST);
        if (CollectionUtils.isNotEmpty(sensitiveWordList)) {
            sensitiveWordTree.addWords(sensitiveWordList);
            List<String> wensitiveWordList = sensitiveWordTree.matchAll(content, -1, false, false);
            sensitiveWordTree.clear();
            return wensitiveWordList;
        }
        return null;
    }
}
